/*
 * 
 *  This file is part of Sourceprojects.org Lycia Copyright 2010 noctarius
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 */
package org.sourceprojects.lycia.internal.configuration;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.sourceprojects.lycia.Delegate;
import org.sourceprojects.lycia.Generator;
import org.sourceprojects.lycia.LyciaConfigurator;
import org.sourceprojects.lycia.LyciaParser;
import org.sourceprojects.lycia.QualifiedName;
import org.sourceprojects.lycia.TagDefinitionParser;
import org.sourceprojects.lycia.annotations.Tag;
import org.sourceprojects.lycia.annotations.TagParser;
import org.sourceprojects.lycia.annotations.TagTransformer;
import org.sourceprojects.lycia.exceptions.LyciaConfigurationException;
import org.sourceprojects.lycia.internal.XPathAwareXmlParserConfigurator;
import org.sourceprojects.lycia.internal.configuration.TagConfigurationUtils.MethodDefinition;
import org.sourceprojects.lycia.internal.logging.LyciaLoggerWrapper;
import org.sourceprojects.lycia.internal.platform.InternalXPathResolver;
import org.w3c.dom.Element;

/**
 * {@link LyciaConfigurator} implementation for handling annotated
 * {@link TagDefinitionParser} like @{@link TagParser} or @
 * {@link TagTransformer}.
 * 
 * @author noctarius
 * @since 1.9.1
 * 
 * @param <B>
 *            Type of ContextObject
 */
public class AnnotatedXmlParserConfigurator<B> extends
		BasicXmlParserConfigurator<B> implements
		XPathAwareXmlParserConfigurator<B> {

	private static final LyciaLoggerWrapper LOGGER = LyciaLoggerWrapper
			.getLogger(AnnotatedXmlParserConfigurator.class);

	private final InternalXPathResolver<B> xPathResolver;

	public AnnotatedXmlParserConfigurator(final LyciaParser<B> xmlParser,
			final LyciaConfigurator<B> parent) {
		super(xmlParser, parent);

		xPathResolver = super.getPlatformFactory().newInternalXPathResolver();
	}

	@Override
	@SuppressWarnings("unchecked")
	public void addTagDefinitionParser(final Object definitionParser) {
		// Now search for annotated methods
		final MethodDefinition[] definitions = TagConfigurationUtils
				.findAnnotatedMethods(definitionParser.getClass());

		// Basic test if definitionParser could be a legal TagDefinitionParser
		if (definitions.length == 0
				&& !(definitionParser instanceof TagDefinitionParser)) {
			throw new LyciaConfigurationException("Class "
					+ definitionParser.getClass().getCanonicalName()
					+ " is no legal TagDefinitionParser");
		}

		// If methods found add them as DelegateTagDefinitionParsers
		if (definitions.length > 0) {
			addAnnotatedTagDefinitionParser(definitionParser, definitions);
		}
	}

	@Override
	public void addTagDefinitionParsers(final Object[] definitionParsers) {
		for (final Object definitionParser : definitionParsers) {
			addTagDefinitionParser(definitionParser);
		}
	}

	@Override
	public void addTagDefinitionParsers(
			final TagDefinitionParser<B>[] definitionParsers) {
		for (final TagDefinitionParser<B> definitionParser : definitionParsers) {
			addTagDefinitionParser(definitionParser);
		}
	}

	@Override
	public void addTagDefinitionParser(
			final TagDefinitionParser<B> definitionParser) {

		// First try to resolve standard methods
		if (definitionParser.getTags() != null
				&& definitionParser.getTags().length > 0) {
			super.addTagDefinitionParser(definitionParser);
		}

		// Now search for annotated methods
		final MethodDefinition[] definitions = TagConfigurationUtils
				.findAnnotatedMethods(definitionParser.getClass());

		if (definitions.length > 0) {
			addAnnotatedTagDefinitionParser(definitionParser, definitions);
		}
	}

	@Override
	public List<TagDefinitionParser<B>> findTagDefinitionParsers(
			final QualifiedName localElement, final Element element) {

		final List<TagDefinitionParser<B>> parsers = new ArrayList<TagDefinitionParser<B>>();
		try {
			// Start adding all standard TagDefinitionParsers
			parsers.addAll(super
					.findTagDefinitionParsers(localElement, element));
		} catch (final LyciaConfigurationException e) {
			if (!xPathResolver.isNamespaceInXPathMappings(localElement
					.getNamespaceURI())) {
				LOGGER.warn("No standard namespace parser for namespaceURI '"
						+ localElement.getNamespaceURI() + "' registered");

				return Collections.emptyList();
			}

			// Use XPath mapped TagDefinitionParsers if there any (in case of
			// exception)
			parsers.addAll(xPathResolver.findXPathMapping(element));
			return parsers;
		}

		// Add XPath mapped TagDefinitionParsers
		parsers.addAll(xPathResolver.findXPathMapping(element));

		return parsers;
	}

	@Override
	public void registerXPathTagDefinitionParser(final Delegate<B> delegate,
			final Tag tag, final String namespaceURI) {

		xPathResolver.registerXPathTagDefinitionParser(delegate, tag,
				namespaceURI);
	}

	@SuppressWarnings("unchecked")
	private void addAnnotatedTagDefinitionParser(
			final Object tagDefinitionParser,
			final MethodDefinition[] definitions) {

		// Retrieve the generator implementation
		final Generator<B> generator = getGeneratorFactory().newInstance(
				getGenerationStrategy());

		// Add for every found method add TagDefinitionParsers created by the
		// generator implementation
		for (final MethodDefinition definition : definitions) {
			final Delegate<B> delegate = generator.generate(
					tagDefinitionParser, definition, getXmlParser());

			if (delegate == null) {
				throw new LyciaConfigurationException(
						"Could not initialize Annotation-Delegate for method "
								+ definition.getMethod().getName()
								+ " on class "
								+ tagDefinitionParser.getClass()
										.getCanonicalName());
			}

			// Add one delegate per Tag
			for (final Tag tag : definition.getTags()) {
				String namespaceURI = tag.namespace();
				if ("".equals(namespaceURI)) {
					if (tagDefinitionParser instanceof TagDefinitionParser<?>) {
						namespaceURI = ((TagDefinitionParser<B>) tagDefinitionParser)
								.getNamespace();
					}
				}

				final boolean legalXpath = !TagConfigurationUtils
						.isNullXPathAnnotation(tag);

				if (legalXpath) {
					xPathResolver.registerXPathTagDefinitionParser(delegate,
							tag, namespaceURI);
				} else {
					checkNamespaceResolver(namespaceURI);

					addTagDefinitionParserToContainer(
							new DelegateTagDefinitionParser<B>(delegate, tag,
									namespaceURI), namespaceURI);
				}
			}
		}
	}

	private void checkNamespaceResolver(final String namespaceURI) {
		if (getXmlParser().getResourceResolver().resolveResource(null,
				namespaceURI, null, null, null) == null) {
			throw new LyciaConfigurationException(new NullPointerException(
					"Namespace " + namespaceURI + " cannot be resolved"));
		}
	}

}
